#include "DBChecker.h"
#include "logger.h"

#include <cstdlib>

namespace{
	//XXX OMG WTF use parser
	CExpert::DBChecker::conn_params conn_str2params(const std::string& conn_str){
		CExpert::DBChecker::conn_params ret;
		size_t driver_end = conn_str.find_first_of(':');
		ret.driver = std::string(conn_str.begin(), conn_str.begin() + driver_end);

        std::string user_and_passwd;
		size_t host_begin = conn_str.find_first_of('@');
        if (host_begin != std::string::npos){
            user_and_passwd = std::string(conn_str.begin() + driver_end + 3, conn_str.begin() + host_begin);
            size_t passwd_begin = user_and_passwd.find_first_of(':');
            ret.username = user_and_passwd;
            if (passwd_begin != std::string::npos){
                ret.username = std::string(user_and_passwd.begin(), user_and_passwd.begin() + passwd_begin);
                ret.passwd = std::string(user_and_passwd.begin() + passwd_begin + 1, user_and_passwd.end());
            }
        } else{
            host_begin = driver_end + 2;
        }

		std::string host_port_db(conn_str.begin() + host_begin + 1, conn_str.end());
		size_t db_begin = host_port_db.find_first_of("/");
        if (db_begin != std::string::npos)
            ret.db = std::string(host_port_db.begin() + db_begin + 1, host_port_db.end());
        else
            db_begin = host_port_db.size();
		size_t host_end = host_port_db.find_first_of(':');
		if (host_end == std::string::npos){
			host_end = db_begin;
            ret.port = "";
        } else{
            ret.port = std::string(host_port_db.begin() + host_end + 1, host_port_db.begin() + db_begin);
        }
		ret.host = std::string(host_port_db.begin(), host_port_db.begin() + host_end);
		return ret;
	}

	std::string conn_str2filename(const std::string& conn_str, bool is_global){
		CExpert::DBChecker::conn_params cp = conn_str2params(conn_str);
		return (is_global?std::string("/"):std::string("")) + cp.driver + "." + cp.username + "." + cp.host + "." + cp.port + "." + cp.db;
	}


}

namespace CExpert{
	std::string DBChecker::connstr2filename(const char* connection_str){
		return conn_str2filename(connection_str, false);
	}

	DBChecker::DBChecker(const char* connection_str, bool is_rw)
		: connection_str_(connection_str)
		, cp_(conn_str2params(connection_str))
		, sh_mem_(conn_str2filename(connection_str, true).c_str(), (is_rw?ShMem::OMWRITE:ShMem::OMREAD))
		, thread_(pthread_t())
		, is_stopped_(false)
        , fail_count_(0)
	{
		if (is_rw)
			ok();
	}

	DBChecker::~DBChecker(){
		if (thread_){
			is_stopped_ = true;
			void* ret_code;
			if (pthread_join(thread_, &ret_code)){
				//std::cout << "FAIL JOIN" << std::endl;
			}
		}
	}

	void DBChecker::start(void *(*start_routine)(void*)){
		if (pthread_create(&thread_, NULL, start_routine, reinterpret_cast<void*>(this)))
			throw DBCheckerException("can't run thread_create");
	}

    void DBChecker::stop(){
        is_stopped_ = true;
    }

    void DBChecker::join(){
        void* ret;
        if (pthread_join(thread_, &ret))
            throw DBCheckerException("can't run pthread_join");
    }

	const DBChecker::conn_params& DBChecker::get_cp() const{
		return cp_;
	}

	bool DBChecker::is_stopped() const{
		return is_stopped_;
	}

	void DBChecker::fail(){
		if (is_ok())
			log("DBChecker try set to FAIL: %s", connection_str_.c_str());
        fail_count_ += 1;
        if (fail_count_ <= 3)
            return;
        log("DBChecker set to FAIL: %s", connection_str_.c_str());
		DBChecker::status* status = reinterpret_cast<DBChecker::status*>(sh_mem_.get_memory());
		status->is_ok = false;
		time(&status->check_time);
	}
	void DBChecker::ok(){
		if (not is_ok())
			log("DBChecker set to OK: %s", connection_str_.c_str());
		DBChecker::status* status = reinterpret_cast<DBChecker::status*>(sh_mem_.get_memory());
		status->is_ok = true;
		time(&status->check_time);
	}
	bool DBChecker::is_ok() const{
		const DBChecker::status* status = reinterpret_cast<const DBChecker::status*>(sh_mem_.get_read_memory());
		//log("DBChecker return %i for: %s", status->is_ok, connection_str_.c_str());
		return status->is_ok;
	}

	DBChecker::status DBChecker::get_status() const{
		const DBChecker::status* status = reinterpret_cast<const DBChecker::status*>(sh_mem_.get_read_memory());
		return *status;
	}

}
